package com.bluexmicro.ota2.ui.screen.dfu

import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanResult
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bluexmicro.bluetooth.BluetoothCentral
import com.bluexmicro.bluetooth.model.ScanMode
import com.bluexmicro.bluetooth.scan.ScannerImpl
import com.bluexmicro.ota.FirmwareHelper
import com.bluexmicro.ota.dfu.DfuProcess
import com.bluexmicro.ota.dfu.ExitAction
import com.bluexmicro.ota.dfu.FirmwareBean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.Exception

class BatchDfuViewModel : ViewModel() {

    val permissions by lazy {
        val array = mutableListOf<String>()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            array.add(android.Manifest.permission.BLUETOOTH_CONNECT)
            array.add(android.Manifest.permission.BLUETOOTH_SCAN)
        }
        array.add(android.Manifest.permission.ACCESS_FINE_LOCATION)
        array.add(android.Manifest.permission.ACCESS_COARSE_LOCATION)
        array
    }
    val items = mutableStateOf<List<FirmwareBean>>(emptyList())

    val current = mutableStateOf("")
    val progress = mutableStateOf(0) //0~100

    val success = mutableStateListOf<String>()
    val failed = mutableStateListOf<String>()

    val updating = mutableStateOf(false)

    val bluetoothException = mutableStateOf(false)
    private var cacheZip: File? = null
    fun onFileSelected(context: Context, zip: File) = viewModelScope.launch(Dispatchers.IO) {
        try {
            items.value = FirmwareHelper.unzipToFirmwares(context, zip)
            cacheZip = zip
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private var job: Job? = null
    fun startUpdate(context: Context, rssi: Int, name: String) {
        job?.takeIf { it.isActive }?.apply { cancel() }
        job = viewModelScope.launch {
            bluetoothException.value = false
            updating.value = true
            while (updating.value) {
                val startTime = System.currentTimeMillis()
                var endTime = 0L
                current.value = ""
                val device = findTarget(context, rssi, name)
                current.value = device.address
                progress.value = 0
                val dfu = DfuProcess()
                val process = try {
                    dfu.initialize(context, device)
                } catch (e: Exception) {
                    e.printStackTrace()
                    if (failed.contains(device.address)) {
                        failed.add(device.address)
                    }
                    null
                }
                var tmpProgress = 0
                process?.let {
                    it.update(cacheZip!!, ExitAction.RebootEnterApp)
                        .catch {
                            if (failed.contains(device.address)) {
                                failed.add(device.address)
                            }
                        }
                        .collect { p ->
                            val tenProgress = (p * 100).toInt()
                            if (tenProgress != tmpProgress) {
                                tmpProgress = tenProgress
                                progress.value = tenProgress
                            }
                        }
                    if (progress.value >= 100) {
                        success.add(device.address)
                    }
                }
                endTime = System.currentTimeMillis()
                val costTime = endTime - startTime
                if (costTime < 15000L) {
                    withContext(Dispatchers.Default) {
                        delay(15000L - costTime)
                    }
                }
                BluetoothCentral.releasePeripheral(context, device.address)
                withContext(Dispatchers.Default) {
                    delay(2000)
                }
            }
        }
    }


    @SuppressLint("MissingPermission")
    private suspend fun findTarget(context: Context, rssi: Int, name: String): BluetoothDevice {
        BluetoothCentral.scanForPeripherals(ScanMode.LOW_LATENCY)
        while (true) {
            withContext(Dispatchers.Default) {
                delay(500)
            }
            val results = BluetoothCentral.fetchScanResults(0L)
            val targets = results.filter {
                val n = if (name.isEmpty() || name.isBlank()) {
                    true
                } else {
                    val n1 = it.device.name?.uppercase()?.contains(name.uppercase()) ?: false
                    val n2 =
                        it.scanRecord?.deviceName?.uppercase()?.contains(name.uppercase()) ?: false
                    n1 || n2
                }
                // rssi name 成功的
                it.rssi > rssi && n && !success.contains(it.device.address)
            }
            if (targets.isNotEmpty()) {
                BluetoothCentral.clearCache()
                BluetoothCentral.stopScan()
                return targets.maxBy { it.rssi }.device
            }
        }
    }

    fun stop() {
        job?.takeIf { it.isActive }?.apply { cancel() }
        updating.value = false
        BluetoothCentral.stopScan()
    }

    private fun checkBluetoothException() = viewModelScope.launch {
        BluetoothCentral.bluetoothError.collect {
            if (it) {
                stop()
                bluetoothException.value = true
            }
        }
    }

    init {
        checkBluetoothException()
    }

    override fun onCleared() {
        super.onCleared()
        stop()
    }

}